#![feature(proc_macro_hygiene)]

mod common;
mod handlers;

use pyo3::prelude::*;
use pyo3::types::PyBytes;
use pyo3::wrap_pyfunction;
use std::hash::Hasher;
use serde::Deserialize;
use crate::common::Record;

#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;

#[derive(Deserialize)]
pub struct HuobiTickForHash {
  #[serde(default)]
  pub mrid: u64,
  #[serde(default)]
  pub id: u64,
}

#[derive(Deserialize)]
struct HuobiHashRecordData {
    ch: String,
    tick: HuobiTickForHash,
}


#[derive(Deserialize)]
pub struct HuobiSpotHash {
  #[serde(default)]
  pub ts: u64,
}


#[derive(Deserialize)]
struct HuobiSpotHashRecordData {
    ch: String,
    tick: HuobiSpotHash,
}

#[pymodule]
fn feed_handler(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<common::BookBuilder>()?;
    m.add_class::<common::Trade>()?;
    m.add_class::<common::PriceQty>()?;
    m.add_class::<handlers::BitmexHandler>()?;
    m.add_class::<handlers::HuobiFuturesHandler>()?;
    m.add_class::<handlers::OkexV3FuturesHandler>()?;
    m.add_class::<handlers::HuobiHandler>()?;
    m.add_wrapped(wrap_pyfunction!(ahash)).unwrap();

    #[pyfn(m, "topic_record_data_hash")]
    fn topic_record_data_hash(py: Python, record: PyObject) -> PyResult<u64> {
        let bytes: PyObject = record.getattr(py, "data")?.call_method0(py, "tobytes")?;
        let bytes: &[u8] = bytes.extract::<&PyBytes>(py)?.as_bytes();
        let bytes: Vec<u8> = bytes.to_vec();
        Ok(ahash_impl(&bytes))
    }

    #[pyfn(m, "huobi_depth_hash")]
    fn huobi_depth_hash(py: Python, record: PyObject) -> PyResult<u64> {
        let record = huobi_to_hash_record(py, record).unwrap();
        let deseri = format!("{}-{}", record.data.ch, record.data.tick.mrid);
        Ok(ahash_impl(&deseri.as_bytes()))
    }
    #[pyfn(m, "huobi_trade_hash")]
    fn huobi_trade_hash(py: Python, record: PyObject) -> PyResult<u64> {
        let record = huobi_to_hash_record(py, record).unwrap();
        let deseri = format!("{}-{}", record.data.ch, record.data.tick.id);
        Ok(ahash_impl(&deseri.as_bytes()))
    }
    #[pyfn(m, "huobi_spot_hash")]
    fn huobi_spot_hash(py: Python, record: PyObject) -> PyResult<u64> {
        let record = huobispot_to_hash_record(py, record).unwrap();
        let deseri = format!("{}-{}", record.data.ch, record.data.tick.ts);
        Ok(ahash_impl(&deseri.as_bytes()))
    }
    Ok(())
}

fn huobispot_to_hash_record(py: Python, record: PyObject) -> PyResult<Record<HuobiSpotHashRecordData>> {
    let bytes: PyObject = record.getattr(py, "data")?.call_method0(py, "tobytes")?;
    let bytes: &[u8] = bytes.extract::<&PyBytes>(py)?.as_bytes();

    let record = Record::<HuobiSpotHashRecordData> {
        timestamp: record.getattr(py, "timestamp")?.extract(py)?,
        data: serde_json::from_slice(bytes).expect("cannot parse json"),
    };
    Ok(record)
}

fn huobi_to_hash_record(py: Python, record: PyObject) -> PyResult<Record<HuobiHashRecordData>> {
    let bytes: PyObject = record.getattr(py, "data")?.call_method0(py, "tobytes")?;
    let bytes: &[u8] = bytes.extract::<&PyBytes>(py)?.as_bytes();

    let record = Record::<HuobiHashRecordData> {
        timestamp: record.getattr(py, "timestamp")?.extract(py)?,
        data: serde_json::from_slice(bytes).expect("cannot parse json"),
    };
    Ok(record)
}

fn ahash_impl(data: &[u8]) -> u64 {
    let mut h = ahash::AHasher::new_with_keys(1234, 5678);
    h.write(data);
    h.finish()
}

#[pyfunction]
fn ahash(data: &PyBytes) -> u64 {
    let data = data.as_bytes();
    ahash_impl(data)
}

// #[pyclass]
// struct BaseHandler {
//    num: i32,
//    debug: bool,
// }

// #[pymethods]
// impl BaseHandler {
//     fn subscribe_book() {

//     }
//     def subscribe_event(self, event_type, keys, callback):
//     assert all([type(key) == str for key in keys])
//     self._event_dist.subscribe(event_type, keys, callback)

//     def subscribe_trade(self, symbols, callback):
//     self.subscribe_event('trade', symbols, callback)
//     def subscribe_feed_status(self, callback):
//     self.subscribe_event('feed_status', 'feed_status', callback)
//     def subscribe_instrument(self, symbols, callback):
//     self.subscribe_event('instrument', symbols, callback)
// }
